﻿using System;

namespace Kz.Telegram.Bot.Infrastructure.Helpers
{
    public class PCMHelper
    {
        private static int SIGN_BIT = 0x80;
        private static int QUANT_MASK = 0xf;
        private static int SEG_SHIFT = 4;
        private static int SEG_MASK = 0x70;
        private static int BIAS = 0x84;
        private static int CLIP = 8159;

        static short[] seg_end = { 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF };

        static short search(short val, short[] table, short size)
        {

            for (short i = 0; i < size; i++)
            {
                if (val <= table[i])
                {
                    return i;
                }
            }
            return size;
        }

        public static byte linear2alaw(short pcm_val)
        {
            short mask;
            short seg;
            char aval;
            if (pcm_val >= 0)
            {
                mask = 0xD5;
            }
            else
            {
                mask = 0x55;
                pcm_val = (short)(-pcm_val - 1);
                if (pcm_val < 0)
                {
                    pcm_val = 32767;
                }
            }

            /* Convert the scaled magnitude to segment number. */
            seg = search(pcm_val, seg_end, 8);

            /* Combine the sign, segment, and quantization bits. */

            if (seg >= 8)       /* out of range, return maximum value. */
                return (byte)(0x7F ^ mask);
            else
            {
                aval = (char)(seg << SEG_SHIFT);
                if (seg < 2)
                    aval |= Convert.ToChar(pcm_val >> 4 & QUANT_MASK);
                else
                    aval |= Convert.ToChar(pcm_val >> seg + 3 & QUANT_MASK);
                return (byte)(aval ^ mask);
            }
        }

        public static byte linear2ulaw(short pcm_val)    /* 2's complement (16-bit range) */
        {
            int mask;
            int seg;
            char uval;

            /* Get the sign and the magnitude of the value. */
            // pcm_val = (short) (pcm_val >> 2);
            if (pcm_val < 0)
            {
                pcm_val = (short)-pcm_val;
                mask = 0x7F;
            }
            else
            {
                mask = 0xFF;
            }
            if (pcm_val > CLIP) pcm_val = (short)CLIP;        /* clip the magnitude 削波*/
            pcm_val += (short)(BIAS >> 2);

            /* Convert the scaled magnitude to segment number. */
            seg = search(pcm_val, seg_end, 8);

            /*
             * Combine the sign, segment, quantization bits;
             * and complement the code word.
             */
            if (seg >= 8)        /* out of range, return maximum value. */
                return (byte)(0x7F ^ mask);
            else
            {
                uval = (char)(seg << 4 | pcm_val >> seg + 3 & 0xF);
                return (byte)(uval ^ mask);
            }
        }

        static short alaw2linear(byte a_val)
        {
            short t;
            short seg;

            a_val ^= 0x55;

            t = (short)((a_val & QUANT_MASK) << 4);
            seg = (short)((a_val & SEG_MASK) >> SEG_SHIFT);
            switch (seg)
            {
                case 0:
                    t += 8;
                    break;
                case 1:
                    t += 0x108;
                    break;
                default:
                    t += 0x108;
                    t <<= seg - 1;
                    break;
            }
            return (a_val & SIGN_BIT) != 0 ? t : (short)-t;
        }

        /**
         * pcm 转 G711 a率
         * @param pcm
         * @param code
         * @param size
         */
        public static void G711aEncoder(short[] pcm, byte[] code, int size)
        {
            for (int i = 0; i < size; i++)
            {
                code[i] = linear2alaw(pcm[i]);
                //Log.e("-------------","数据编码");
            }
        }

        /**
         * G.711 转 PCM
         * @param pcm
         * @param code
         * @param size
         */
        public static void G711aDecoder(short[] pcm, byte[] code, int size)
        {
            for (int i = 0; i < size; i++)
            {
                pcm[i] = alaw2linear(code[i]);
            }
        }
        /**
         * pcm 转 G711 u率
         * @param pcm
         * @param code
         * @param size
         */
        public static void G711uEncoder(short[] pcm, byte[] code, int size)
        {
            for (int i = 0; i < size; i++)
            {
                code[i] = linear2ulaw(pcm[i]);
                //Log.e("-------------","数据编码");
            }
        }

    }
}
